package com.xored.glance.search;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.Preferences;

import com.xored.glance.core.ITextBlock;
import com.xored.glance.internal.core.GlancePlugin;
import com.xored.glance.internal.core.IConstants;
import com.xored.glance.search.engine.ISearchListener;
import com.xored.glance.search.engine.Match;
import com.xored.glance.search.engine.SearchEngine;

/**
 * @author Yuri Strot
 * 
 */
public class SearchManager implements ISearchListener {

	private SearchEngine engine;
	private ISearchPanel panel;
	private SearchUnit[] blocks;

	private Map<ITextBlock, SearchUnit> blockToWrappers = new HashMap<ITextBlock, SearchUnit>();

	private Match[] matches;
	private int type;
	private int index;
	/** Last found text */
	private String text;
	/** Last not found text */
	private boolean matchCase;
	private boolean prefix;

	/** Searching from the current position */
	public static final int FIND_HERE = 0;
	/** Searching next occurrence of the string */
	public static final int FIND_NEXT = 1;
	/** Searching previous occurrence of the string */
	public static final int FIND_PREVIOUS = 2;

	public SearchManager(ISearchPanel panel) {
		this.panel = panel;
		initBlocks();
		engine = new SearchEngine(this);
		engine.start();
		prefix = getPreferences().getBoolean(IConstants.WORD_PREFIX);
		matchCase = getPreferences().getBoolean(IConstants.MATCH_CASE);
	}

	protected Preferences getPreferences() {
		return GlancePlugin.getDefault().getPluginPreferences();
	}

	private void initBlocks() {
		ITextBlock[] blocks = panel.getText();
		this.blocks = new SearchUnit[blocks.length];
		int offset = 0;
		for (int i = 0; i < blocks.length; i++) {
			SearchUnit wrapper = new SearchUnit(blocks[i], offset);
			offset += wrapper.getLength();
			this.blocks[i] = wrapper;
			blockToWrappers.put(blocks[i], wrapper);
		}
	}

	public void find(String text, int type) {
		this.type = type;
		if (this.text != null && text.equals(this.text)) {
			updateSelection();
		} else {
			index = 0;
			matches = null;
			Pattern pattern = SearchEngine.createPattern(text, matchCase,
					prefix);
			engine.setMatcher(pattern, prefix, blocks, index, true);
		}
		this.text = text;
	}

	protected void updateSearch() {
		matches = null;
		if (text != null && text.length() > 0) {
			Pattern pattern = SearchEngine.createPattern(text, matchCase,
					prefix);
			engine.setMatcher(pattern, prefix, blocks, index, false);
		} else {
		}
	}

	protected void findMatch(int index) {
		findMatch(matches[index]);
	}

	protected void findMatch(Match match) {
		if (match != null) {
			index = getOffset(match);
		}
		panel.find(match);
	}

	protected void updateSelection() {
		if (matches != null) {
			if (type == FIND_NEXT) {
				for (int i = 0; i < matches.length; i++) {
					if (getOffset(matches[i]) > index) {
						findMatch(i);
						return;
					}
				}
				if (matches.length > 0) {
					findMatch(0);
				}
			} else if (type == FIND_PREVIOUS) {
				for (int i = matches.length - 1; i >= 0; i--) {
					if (getOffset(matches[i]) < index) {
						findMatch(i);
						return;
					}
				}
				if (matches.length > 0) {
					findMatch(matches.length - 1);
				}
			}
		}
	}

	protected int getOffset(Match match) {
		SearchUnit wrapper = (SearchUnit) match.getSearchUnit();
		return wrapper.getOffset() + match.getOffset();
	}

	public void dispose() {
		engine.exit();
		engine = null;
	}

	/**
	 * Set case sensitive search
	 * 
	 * @param matchCase
	 *            true performs a case sensitive search, false an insensitive
	 *            search
	 */
	public void setMatchCase(boolean matchCase) {
		this.matchCase = matchCase;
		updateSearch();
	}

	/**
	 * Set word prefix search
	 * 
	 * @param prefix
	 *            if true only occurrences are reported in which the string
	 *            stands as a word by itself
	 */
	public void setPrefix(boolean prefix) {
		this.prefix = prefix;
		updateSearch();
	}

	public void allFound(Match[] matches) {
		this.matches = matches;
		updateSelection();
	}

	public void finished() {
		panel.match(matches);
	}

	public void firstFound(Match match) {
		if (type == FIND_HERE)
			findMatch(match);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.xored.everywhere.search.IFindListener#clear()
	 */
	public void clear() {
		panel.clear();
		matches = null;
	}

}
